﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.ComponentModel;
using Tsanie.FlvTag;
using Tsanie.Properties;
using Tsanie.ShellExtension;
using Tsanie.ShellExtension.PropertySheet;
using Tsanie.ShellExtension.PropertySheet.UI;
using Tsanie.ShellExtension.Util;

namespace Tsanie {
    [Guid("CABA10D7-965D-4552-9D7B-D7E3D7534AC8")]
    [ComVisible(true), ExtensionFileTypes(".flv,.hlv", FindAssoc = true)]
    public class FlvPropertySheet : PropertySheetBase {

        #region - Fields -

        private FlvTagReader _reader;
        private List<ListViewItem> _cache;
        private Thread _thread;
        private System.Timers.Timer _timer;
        private ITaskbarList3 _taskbar;

        private GroupBox groupParam;
        private RadioButton radioBitrate;
        private RadioButton radioTime;
        private RadioButton radioRate;
        private NumericUpDown numericBitrate;
        private NumericUpDown numericTime;
        private NumericUpDown numericRate;
        private GroupBox groupMethod;
        private RadioButton radioBlackMethod;
        private RadioButton radioRateMethod;
        private RadioButton radioForeMethod;
        private ButtonEx buttonExecute;
        private ProgressBar progress;
        private ButtonEx buttonCancel;
        private Label labelStatus;
        private ButtonEx buttonReload;
        private ListViewEx listFrames;

        #endregion

        #region - .Ctor -

        public FlvPropertySheet()
            : base() {
            _reader = null;
            this.Text = Lang.PageTitle;
            _taskbar = null;
            if (Win7Stuff.IsWin7)
                _taskbar = (ITaskbarList3)new ProgressTaskbar();
        }

        #endregion

        #region - Overrides -

        protected override string[] AllowExtensions { get { return new string[] { ".flv", ".hlv" }; } }
        protected override void OnCreatePage() {
            base.OnCreatePage();
            _cache = new List<ListViewItem>();
            InitComponent();
            listFrames.Columns.AddRange(new ColumnHeader[] {
                new ColumnHeader() { Name = "colNo", Text = "#", Width = 40 },
                new ColumnHeader() { Name = "colType", Text = Lang.ColType, Width = 62 },
                new ColumnHeader() { Name = "colTime", Text = Lang.ColTime, Width = 76 },
                new ColumnHeader() { Name = "colCodec", Text = Lang.ColCodec, Width = 70 },
                new ColumnHeader() { Name = "colFrameType", Text = Lang.ColFrameType, Width = 80 },
                new ColumnHeader() { Name = "colOffset", Text = Lang.ColOffset, Width = 80 },
                new ColumnHeader() { Name = "colSize", Text = Lang.ColSize, Width = 70 }
            });
            Win7Stuff.SetWindowTheme(listFrames.Handle, "explorer", null);
            LoadFlvTags();
        }

        #endregion

        #region - Private Methods -

        private void LoadFlvTags() {
            long pos = 0;
            int n = 0;
            _timer = _timer.CreateTimer(200, (e) => { SetProgressValue(pos, 0); });
            _reader = new FlvTagReader(this.Files[0]) {
                MaximumGetted = (maximum) => { SetProgressValue(0, maximum); },
                Done = (count) => {
                    SetProgressState(TBPFLAG.TBPF_NOPROGRESS);
                    listFrames.VirtualListSize = count;
                    _timer.Cancel();
                    buttonCancel.Action = null;
                    SetFormatText();
                    EnabledUI(true);
                },
                Predicate = (tag) => {
                    n++;
                    ListViewItem item = new ListViewItem(new string[] {
                        n.ToString(),
                        tag.Type.ToString(),
                        tag.TimeStamp.ToTimeString(),
                        tag.Info1,
                        tag.Info2,
                        string.Format("0x{0:X8}", tag.Offset),
                        tag.DataSize.ToString()
                    });
                    if (tag is AudioTag)
                        item.ForeColor = Color.Gray;
                    _cache.Add(item);
                    pos = _reader.Seek;
                    return true;
                }
            };
            _cache.Clear();
            _thread.Cancel();
            this.labelStatus.Text = Lang.StatusLoading;
            EnabledUI(false);
            _thread = _reader.StartRead();
            if (_thread == null) {
                // 读取失败，不是flv文件等
                buttonCancel.Enabled = false;
                buttonReload.Enabled = false;
                Common.OutputDebugString("LoadFlvTags(void):: not a flv file.");
                return;
            }
            SetProgressState(TBPFLAG.TBPF_NORMAL);
            SetProgressValue(0, 0);
            _timer.Start();
            buttonCancel.Action = delegate {
                _thread.Cancel();
                _timer.Cancel();
                int count = _cache.Count;
                listFrames.VirtualListSize = count;
                EnabledUI(true);
                Common.OutputDebugString("LoadFlvTags(void):: Abort to read tags. now list count is {0}.", count);
            };
        }
        private void SetFormatText() {
            labelStatus.Text = string.Format("{0} {1}, {2} {3:0.0}Kbps", new object[] {
                Lang.StatusDuration,
                _reader.Duration.ToTimeString(),
                Lang.StatusRate,
                _reader.Rate
            });
        }
        private void EnabledUI(bool enabled) {
            this.SafeRun(delegate {
                listFrames.Enabled = enabled;
                groupParam.Enabled = enabled;
                groupMethod.Enabled = enabled;
                buttonExecute.Enabled = enabled;
            });
        }
        private void SetProgressState(TBPFLAG style) {
            this.SafeRun(delegate {
                switch (style) {
                    case TBPFLAG.TBPF_ERROR:
                    case TBPFLAG.TBPF_NORMAL:
                    case TBPFLAG.TBPF_PAUSED:
                        progress.Style = ProgressBarStyle.Blocks;
                        progress.Visible = true;
                        buttonCancel.Enabled = true;
                        break;
                    case TBPFLAG.TBPF_INDETERMINATE:
                        progress.Style = ProgressBarStyle.Marquee;
                        progress.Visible = true;
                        buttonCancel.Enabled = true;
                        break;
                    case TBPFLAG.TBPF_NOPROGRESS:
                        progress.Visible = false;
                        buttonCancel.Enabled = false;
                        break;
                }
            });
        }
        private void SetProgressValue(long completed, long total) {
            this.SafeRun(delegate {
                if (total > 0 && total != progress.Maximum)
                    progress.Maximum = (int)total;
                if (completed > progress.Maximum)
                    progress.Value = progress.Maximum;
                else
                    progress.Value = (int)completed;
            });
        }
        private string GetLanguage(string value) {
            value = value.Substring(value.IndexOf('>') + 1);
            StringBuilder builder = new StringBuilder(0x40);
            foreach (string s in value.Split('#')) {
                string val = Lang.ResourceManager.GetString(s);
                builder.Append(val == null ? s : val);
            }
            return builder.ToString();
        }
        private MessageBoxIcon GetMsgIcon(string value) {
            if (value[0] == '<') {
                int n = value.IndexOf('>', 1);
                if (n > 0) {
                    string type = value.Substring(1, n - 1);
                    switch (type) {
                        case "Exception":
                            return MessageBoxIcon.Error;
                        case "Warning":
                            return MessageBoxIcon.Warning;
                        case "Question":
                            return MessageBoxIcon.Question;
                        case "Info":
                            return MessageBoxIcon.Information;
                    }
                }
            }
            return MessageBoxIcon.Information;
        }

        #endregion

        #region - Events -

        private void ParamCheckedChanged(object sender, EventArgs e) {
            string name = ((RadioButton)sender).Name;
            numericBitrate.Enabled = false;
            numericTime.Enabled = false;
            numericRate.Enabled = false;
            switch (name) {
                case "radioBitrate":
                    numericBitrate.Enabled = true;
                    break;
                case "radioTime":
                    numericTime.Enabled = true;
                    break;
                case "radioRate":
                    numericRate.Enabled = true;
                    break;
            }
        }
        private void MethodCheckedChanged(object sender, EventArgs e) {
            string name = ((RadioButton)sender).Name;
            radioBitrate.Enabled = true;
            if (radioBitrate.Checked)
                numericBitrate.Enabled = true;
            radioTime.Enabled = true;
            if (radioTime.Checked)
                numericTime.Enabled = true;
            radioRate.Enabled = true;
            if (radioRate.Checked)
                numericRate.Enabled = true;
            switch (name) {
                case "radioBlackMethod":
                case "radioForeMethod":
                    radioRate.Enabled = false;
                    if (radioRate.Checked)
                        radioBitrate.Checked = true;
                    break;
                case "radioRateMethod":
                    break;
            }
        }
        private void Execute_OnClick(object sender, EventArgs e) {
            SetProgressState(TBPFLAG.TBPF_INDETERMINATE);
            FlvTagWriter writer = new FlvTagWriter() {
                Tags = _reader,
                BitRate = radioBitrate.Checked ? (double)numericBitrate.Value : -1,
                Time = radioTime.Checked ? (double)numericTime.Value : -1,
                Speed = radioRate.Checked ? (double)numericRate.Value : -1,
                Done = (msg) => {
                    this.SafeRun(delegate {
                        MessageBox.Show(this, GetLanguage(msg), Lang.ButtonExecute, MessageBoxButtons.OK, GetMsgIcon(msg));
                    });
                    SetProgressState(TBPFLAG.TBPF_NOPROGRESS);
                    EnabledUI(true);
                }
            };
            buttonCancel.Action = delegate {
                _thread.Cancel();
                EnabledUI(true);
            };
            EnabledUI(false);
            if (radioBlackMethod.Checked)
                _thread = writer.BlackExecute();
            else if (radioForeMethod.Checked)
                _thread = writer.FrontTsundere();
            else if (radioRateMethod.Checked)
                _thread = writer.RateExecute();
        }
        private void Cancel_OnClick(object sender, EventArgs e) {
            SetProgressState(TBPFLAG.TBPF_NOPROGRESS);
        }
        private void Reload_OnClick(object sender, EventArgs e) {
            LoadFlvTags();
        }
        private void listFrames_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) {
            try {
                e.Item = _cache[e.ItemIndex];
            } catch (Exception ex) {
                Common.OutputDebugString("RetrieveVirtualItem():: {0} - {1}, index: {2}",
                    ex.GetType().FullName,
                    ex.Message,
                    e.ItemIndex);
                listFrames.VirtualListSize = _cache.Count;
                //e.Item = FlvPropertySheet.s_defaultItem;
            }
        }
        private void listFrames_MouseDoubleClick(object sender, MouseEventArgs e) {
            if (e.Button == System.Windows.Forms.MouseButtons.Left && e.Clicks == 2) {
                if (listFrames.SelectedIndices.Count > 0) {
                    int index = listFrames.SelectedIndices[0];
                    MessageBox.Show(this, _reader.Tags[index].ToString(), "Tag", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        #endregion

        #region - Designer -

        private void InitComponent() {
            this.listFrames = new ListViewEx();
            this.groupParam = new GroupBox();
            this.radioBitrate = new RadioButton();
            this.radioTime = new RadioButton();
            this.radioRate = new RadioButton();
            this.numericBitrate = new NumericUpDown();
            this.numericTime = new NumericUpDown();
            this.numericRate = new NumericUpDown();
            this.groupMethod = new GroupBox();
            this.radioBlackMethod = new RadioButton();
            this.radioForeMethod = new RadioButton();
            this.radioRateMethod = new RadioButton();
            this.buttonExecute = new ButtonEx();
            this.progress = new ProgressBar();
            this.buttonCancel = new ButtonEx();
            this.labelStatus = new Label();
            this.buttonReload = new ButtonEx();

            Label labelKbps = new Label();
            Label labelTime = new Label();
            Label labelRate = new Label();

            this.groupParam.SuspendLayout();
            ((ISupportInitialize)(this.numericBitrate)).BeginInit();
            ((ISupportInitialize)(this.numericTime)).BeginInit();
            ((ISupportInitialize)(this.numericRate)).BeginInit();
            this.groupMethod.SuspendLayout();
            this.SuspendLayout();
            // 
            // listFrames
            // 
            this.listFrames.Location = new Point(8, 26);
            this.listFrames.Font = new Font(this.Font.FontFamily, 8.25f, FontStyle.Regular, GraphicsUnit.Point);
            this.listFrames.Size = new Size(this.Width - 19, this.Height - 146);
            this.listFrames.UseCompatibleStateImageBehavior = false;
            this.listFrames.VirtualMode = true;
            this.listFrames.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(this.listFrames_RetrieveVirtualItem);
            this.listFrames.MouseDoubleClick += new MouseEventHandler(this.listFrames_MouseDoubleClick);
            // 
            // groupParam
            // 
            this.groupParam.Controls.Add(this.radioBitrate);
            this.groupParam.Controls.Add(this.radioTime);
            this.groupParam.Controls.Add(this.radioRate);
            this.groupParam.Controls.Add(this.numericBitrate);
            this.groupParam.Controls.Add(labelKbps);
            this.groupParam.Controls.Add(this.numericTime);
            this.groupParam.Controls.Add(labelTime);
            this.groupParam.Controls.Add(this.numericRate);
            this.groupParam.Controls.Add(labelRate);
            this.groupParam.Location = new Point(8, this.Height - 98);
            this.groupParam.Size = new Size(this.Width - 208, 92);
            this.groupParam.Text = Lang.GroupParam;
            this.groupParam.TabStop = false;
            // 
            // radioBitrate
            // 
            this.radioBitrate.AutoSize = true;
            this.radioBitrate.Checked = true;
            this.radioBitrate.Location = new Point(6, 21);
            this.radioBitrate.Name = "radioBitrate";
            this.radioBitrate.Size = new Size(14, 13);
            this.radioBitrate.TabStop = true;
            this.radioBitrate.UseVisualStyleBackColor = true;
            this.radioBitrate.CheckedChanged += new EventHandler(this.ParamCheckedChanged);
            // 
            // radioTime
            // 
            this.radioTime.AutoSize = true;
            this.radioTime.Location = new Point(6, 43);
            this.radioTime.Name = "radioTime";
            this.radioTime.Size = new Size(14, 13);
            this.radioTime.UseVisualStyleBackColor = true;
            this.radioTime.CheckedChanged += new EventHandler(this.ParamCheckedChanged);
            // 
            // radioRate
            // 
            this.radioRate.AutoSize = true;
            this.radioRate.Enabled = false;
            this.radioRate.Location = new Point(6, 64);
            this.radioRate.Name = "radioRate";
            this.radioRate.Size = new Size(14, 13);
            this.radioRate.UseVisualStyleBackColor = true;
            this.radioRate.CheckedChanged += new EventHandler(this.ParamCheckedChanged);
            // 
            // numericBitrate
            // 
            this.numericBitrate.Location = new Point(26, 18);
            this.numericBitrate.Maximum = 4096M;
            this.numericBitrate.Minimum = 1M;
            this.numericBitrate.Size = new Size(this.Width - 286, 19);
            this.numericBitrate.Value = 1000M;
            // 
            // labelKbps
            // 
            labelKbps.AutoSize = true;
            labelKbps.Text = Lang.LabelKbps;
            labelKbps.Location = new Point(this.Width - 259, 21);
            // 
            // numericTime
            // 
            this.numericTime.DecimalPlaces = 1;
            this.numericTime.Enabled = false;
            this.numericTime.Location = new Point(26, 40);
            this.numericTime.Maximum = 86400M;
            this.numericTime.Minimum = -86400M;
            this.numericTime.Size = new Size(this.Width - 286, 19);
            this.numericTime.Value = 420M;
            // 
            // labelTime
            // 
            labelTime.AutoSize = true;
            labelTime.Text = Lang.LabelTime;
            labelTime.Location = new Point(this.Width - 259, 43);
            // 
            // numericRate
            // 
            this.numericRate.DecimalPlaces = 2;
            this.numericRate.Enabled = false;
            this.numericRate.Location = new Point(26, 62);
            this.numericRate.Minimum = 0.01M;
            this.numericRate.Size = new Size(this.Width - 286, 19);
            this.numericRate.Value = 3M;
            // 
            // labelRate
            // 
            labelRate.AutoSize = true;
            labelRate.Text = Lang.LabelRate;
            labelRate.Location = new Point(this.Width - 259, 65);
            // 
            // groupMethod
            // 
            this.groupMethod.Controls.Add(this.radioBlackMethod);
            this.groupMethod.Controls.Add(this.radioForeMethod);
            this.groupMethod.Controls.Add(this.radioRateMethod);
            this.groupMethod.Location = new Point(this.Width - 195, this.Height - 98);
            this.groupMethod.Size = new Size(111, 90);
            this.groupMethod.Text = Lang.GroupMethod;
            this.groupMethod.TabStop = false;
            // 
            // radioBlackMethod
            // 
            this.radioBlackMethod.AutoSize = true;
            this.radioBlackMethod.Checked = true;
            this.radioBlackMethod.Location = new Point(6, 19);
            this.radioBlackMethod.Name = "radioBlackMethod";
            this.radioBlackMethod.Text = Lang.RadioBlackMethod;
            this.radioBlackMethod.Size = new Size(14, 13);
            this.radioBlackMethod.TabStop = true;
            this.radioBlackMethod.UseVisualStyleBackColor = true;
            this.radioBlackMethod.CheckedChanged += new EventHandler(this.MethodCheckedChanged);
            // 
            // radioForeMethod
            // 
            this.radioForeMethod.AutoSize = true;
            this.radioForeMethod.Location = new Point(6, 41);
            this.radioForeMethod.Name = "radioForeMethod";
            this.radioForeMethod.Text = Lang.RadioForeMethod;
            this.radioForeMethod.Size = new Size(14, 13);
            this.radioForeMethod.UseVisualStyleBackColor = true;
            this.radioForeMethod.CheckedChanged += new EventHandler(this.MethodCheckedChanged);
            // 
            // radioRateMethod
            // 
            this.radioRateMethod.AutoSize = true;
            this.radioRateMethod.Location = new Point(6, 62);
            this.radioRateMethod.Name = "radioRateMethod";
            this.radioRateMethod.Text = Lang.RadioRateMethod;
            this.radioRateMethod.Size = new Size(14, 13);
            this.radioRateMethod.UseVisualStyleBackColor = true;
            this.radioRateMethod.CheckedChanged += new EventHandler(this.MethodCheckedChanged);
            // 
            // buttonExecute
            // 
            this.buttonExecute.Location = new Point(this.Width - 80, this.Height - 31);
            this.buttonExecute.Size = new Size(69, 23);
            this.buttonExecute.Text = Lang.ButtonExecute;
            this.buttonExecute.FlatStyle = FlatStyle.System;
            this.buttonExecute.UseVisualStyleBackColor = true;
            this.buttonExecute.Click += new EventHandler(this.Execute_OnClick);
            // 
            // progress
            // 
            this.progress.Location = new Point(8, this.Height - 116);
            this.progress.Size = new Size(this.Width - 19, 13);
            this.progress.Visible = false;
            // 
            // buttonCancel
            // 
            this.buttonCancel.Enabled = false;
            this.buttonCancel.Location = new Point(this.Width - 80, this.Height - 92);
            this.buttonCancel.Size = new Size(69, 23);
            this.buttonCancel.Text = Lang.ButtonCancel;
            this.buttonCancel.FlatStyle = FlatStyle.System;
            this.buttonCancel.UseVisualStyleBackColor = true;
            this.buttonCancel.Click += new EventHandler(this.Cancel_OnClick);
            // 
            // labelStatus
            // 
            this.labelStatus.AutoSize = true;
            this.labelStatus.Location = new Point(6, 5);
            // 
            // buttonReload
            // 
            this.buttonReload.Location = new Point(this.Width - 80, this.Height - 60);
            this.buttonReload.Size = new Size(69, 23);
            this.buttonReload.Text = Lang.ButtonReload;
            this.buttonReload.FlatStyle = FlatStyle.System;
            this.buttonReload.UseVisualStyleBackColor = true;
            this.buttonReload.Click += new EventHandler(this.Reload_OnClick);
            // 
            // FlvPropertySheet
            // 
            this.Controls.Add(this.labelStatus);
            this.Controls.Add(this.listFrames);
            this.Controls.Add(this.progress);
            this.Controls.Add(this.groupParam);
            this.Controls.Add(this.groupMethod);
            this.Controls.Add(this.buttonExecute);
            this.Controls.Add(this.buttonReload);
            this.Controls.Add(this.buttonCancel);

            this.groupParam.ResumeLayout(false);
            ((ISupportInitialize)(this.numericBitrate)).EndInit();
            ((ISupportInitialize)(this.numericTime)).EndInit();
            ((ISupportInitialize)(this.numericRate)).EndInit();
            this.groupMethod.ResumeLayout(false);
            this.groupMethod.PerformLayout();
            this.ResumeLayout(false);
        }

        #endregion

    }

    public enum ProgressStyle {
        Blocks = 0,
        Continuous = 1,
        Marquee = 2,
        None = 9
    }
}
